<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>响应式原理</title>
  </head>
  <body>
    <div id="app">
      <p>{{ num }}</p>
      <button @click="handleClick">按钮</button>
    </div>
    <script src="./vue.js"></script>
    <script>
      /*
        1. new Vue 做了什么事？
          beforeCreate
          initState -> 数据代理、数据劫持
          created
          $mount()
          beforeMount
          updateComponent = () => {
            vm._update(vm._render(), xxx);
          }
          new Watcher(vm, updateComponent, ...) -> new Watcher内部会调用updateComponent方法，到此页面完成渲染
          mounted

        2. 数据代理：将props、data、methods、computed数据代理到this上，可以通过this直接访问数据

          methods ->
            for (var key in methods) {
              vm[key] = methods[key].bind(vm)
            }
          
          props\data ->
            const keys = Object.keys(data)
            const i = keys.length;
            while(i--) {
              const key = keys[i];
              proxy(vm, '_data', key)
            }
          
            const proxy = (target, sourceKey, key) => {
              Object.defineProperty(target, key, {
                get() {
                  return this[sourceKey][key]
                },
                set(val) {
                  this[sourceKey][key] = val;
                }
              })
            }

        数据劫持：
          observe(data)
          new Observer(data)
          遍历data所有数据，通过defineReactive方法重新定义属性
            const keys = Object.keys(data) 
            for (var i = 0; i < keys.length; i++) {
              defineReactive(data, keys[i])
            }
          defineReactive
            const dep = new Dep() // 每一个data属性都会生成dep对象
            const val = data[key]
            observe(val) // 递归调用，确保data所有属性都要重新定义
            Object.defineProperty(data, key, {
              get() {
                if (Dep.target) {
                  dep.depend() // 建立响应式联系（建立dep和watcher的联系）
                }
                return val;
              },
              set(newVal) {
                val = newVal; // 更新数据的值
                dep.notify() // 重新渲染页面，达到响应式
              }
            })


        3. 响应式：更新数据，数据变化，同时用户界面也会变化


        总结：
          1. 数据代理：将props/data/methods等中数据代理到this，可以通过this直接访问数据。
            例如data: initState / proxy
              遍历data中的数据，通过Object.defineProperty方法，将data中属性定义在this，此时通过this直接访问数据了，同时定义访问属性get、set方法，
                当读取数据代理的属性时，触发get，实际上读取原data数据的值（this._data.xxx）
                当更新数据代理的属性时，触发set，实际上更新原data数据的值（this._data.xxx = xxx）

          2. 数据劫持：遍历原data数据，将属性进行重新定义，定义成响应式属性
            observe / defineReactive
            调用observe方法开始，遍历原data中的数据，defineReactive将属性进行重新定义，定义成响应式属性
            defineReactive内部会创建一个dep对象，通过observe对属性的值进行递归调用，为了让所有属性都变成响应式
            Object.defineProperty方法重新定义data数据中每一个属性，定义属性get、set方法
              当读取data的属性时，触发get, 返回数据的值，还会调用dep.depend方法，建立响应式联系（dep保存watcher，watcher保存dep）
              当更新data的属性时，触发set, 更新数据的值，还会调用dep.notify方法，触发用户界面更新，从而达到响应式

          3. 模板页面解析渲染：
          
            new Watcher，内部会调用更新用户界面方法
              得到标签字符串，将标签字符串转化为虚拟DOM树（vm._render()），将虚拟DOM树转化为真实DOM元素，插入根标签中生效(vm._update())，完成页面初始化渲染

            建立响应式联系时机：
              将标签字符串转化为虚拟DOM树时，需要读取模板语法表达式的值，此时会触发数据劫持给数据设置的get方法，从而调用dep.depend方法，建立响应式联系
      
          将来当你更新响应式数据时，首先触发数据代理set，里面更新原data数据，此时触发数据劫持set, 同步更新数据的值，调用dep.notify
          dep.notify方法使用 nextTick 方法将 watcher 塞到异步队列去等待将来执行，等主线程执行完所有同步代码，就会执行异步代码，
          执行异步代码找到watcher，调用其更新用户界面的方法更新用户界面，从而达到响应式
      */
      debugger;
      new Vue({
        data() {
          return {
            num: 0,
            person: {
              name: "jack",
              age: 18,
            },
          };
        },
        methods: {
          handleClick() {
            debugger;
            this.num++;
          },
        },
      }).$mount("#app");
    </script>
  </body>
</html>
